從前面Model的文章可以得知,Model繼承base_layer,代表Model也可以當作Layer使用,可以執行 build與 Call。現在要嘗試自訂義Model類別,讓模型可以多樣性、彈性的宣告。
可以繼承的模型類別有 keras.engine.training.Model、keras.engine.sequential.Sequential、keras.engine.functional、keras.engine.base_layer 等等,不管那種類別,建構模型最重要也最需要實作的事項就是層的查增刪改、建構模型、模型訓練、模型計算框架等等。可以參考 模型執行fit的運作文章。基本上這些類別幾乎都繼承keras.engine.base_layer,所以稱模型也可以是Layer的一種也不為過。而模型類別與層的差異主要在於,模型是類似Container容器般的主導管理層的行為,而層卻是執行細部工作的角色。
模型建構與訓練通常都要經過模型類別與層的call 函式,所以這邊大多是要迭代整個模型來逐一呼叫並做串聯,而都會執行模型實體的 keras.engine.training.Model.step_function函式。所以自定義模型可以實作的重要部分可以是 Call 與 step_function 函式。
再看一次模型類別:
第一個範例,自定義模型類別:
自定義Model類別程式
InputsShape = keras.Input(shape=(None,784))
class MyModel(keras.Model):
def __init__(self,inputs):
super().__init__()
dense1_Output = layers.Dense(64, activation="sigmoid")(inputs)
dense2_Output = layers.Dense(10, activation="softmax")(dense1_Output)
self.SubModel = keras.Model(inputs, dense2_Output)
def call(self, inputs):
return self.SubModel(inputs)
model = MyModel(inputs = InputsShape)
model.compile(optimizer="rmsprop",
loss="sparse_categorical_crossentropy",
metrics=["accuracy"])
model.fit(train_images, train_labels)
說明:
起一個自定義模型類別,因為是繼承 keras.engine.training.Model,初始建構仍會到自身的
MyModel.init ,執行到了以下程式:
self.SubModel = Model(inputs, dense2_Output)
是委派了一個新的 keras.engine.training.Model 類別並指定到 self.SubModel,所以如上圖這部分會接續跑
到橘色區域,如同 Model vs Sequence 文章中 ,產生 Model 實體的敘述。
執行訓練時,最重要的執行部分是 Train_Step,會從 MyModel.call 執行然後迭代到每層 layer.call,一樣會檢查是否每層都build過產生設定與初始Weight。 由於因為 init 宣告委派了新的keras.engine.training.Model ,以層的呼叫做串聯,代表已經執行過build的動作(參考 Model vs Sequence 文章中初步執行 Layer 設定部分 ),所以 Model.call 接續橘色部分執行 委派的 keras.engine.training.Model.call(inputs) 。 橘色部分的流程,因為已經 build 過,所以會到 keras.engine.functional.Functional._run_internal_graph 逐一迭代執行每層的Layer.call 函式。最後將結果張量傳出去,接續計算loss、更新權重的update_state等動作。
第二個範例,把Model當成Layer,實作它的Call函式去回傳Layer實體,或使用另一個可作初始化的類別,實作__Call__回傳Layer實體也可行。
把Model當成Layer程式:
import tensorflow as tf
from tensorflow.keras import layers
from tensorflow.keras.models import Model
class MyLayer(Model):
def __init__(self):
super().__init__()
self.dense = layers.Dense(10, activation="softmax")
def call(self, inputs):
return self.dense(inputs)
inputs = layers.Input(shape=(None, 784))
features = layers.Dense(64, activation="relu")(inputs)
outputs = MyLayer()(features)
model = tf.keras.Model(inputs=inputs, outputs=outputs)
model.compile(optimizer="rmsprop",
loss="sparse_categorical_crossentropy",
metrics=["accuracy"])
model.fit(train_images, train_labels)
說明:
這邊自定義一個子類別為 MyModel 並繼承 keras.engine.training.Model,主要實作 call 函式,而 keras.engine.training.Model繼承keras.engine.base_layer。 在模型初始化時,會接收inputs層、 outputs層之參數:
model = keras.Model(inputs=inputs, outputs=outputs)
而傳入參數特別是 outputs,是用自定義類別回傳的;此自定義類別 MyModel 初始化時先建立一個 Dense 層,於MyModel.call 函式 利用此Dense層的 call函式接收額外串接好的層,利用 Dense.call 將所有的層串聯起來(可參考Model vs Sequence之文設定layer的另一種方式),並回傳供給keras.Model使用。
所以在 MyModel之 init 至 call 最後回傳前,可以實行一些 層的設定、輸入資料的處理preprocess動作。
以上是自定義模組的範例,可以透過運作了解未來可以擴充或額外執行那些動作,與執行的時機。